package com.significantfiles.snapshot.engine.compiler;

import java.util.ArrayList;
import java.util.List;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.NotFoundException;

import com.significantfiles.snapshot.engine.compiler.model.x.EntityInterfaceIsBrokenException;
import com.significantfiles.snapshot.engine.structure.DefaultEntity;

public class EntityClassGenerator {

	private final List<EntityFieldGenerator> fields = new ArrayList<EntityFieldGenerator>(
			10);

	private final CtClass modelInterface;

	public EntityClassGenerator(final CtClass modelDef) {
		if (modelDef == null)
			throw new NullPointerException();

		this.modelInterface = modelDef;
	}

	public void addFieldGenerator(final EntityFieldGenerator fieldGenerator) {
		if (fieldGenerator == null)
			throw new NullPointerException();

		this.fields.add(fieldGenerator);
	}

	public Class<?> createClass() throws EntityInterfaceIsBrokenException {
		final ClassPool classPool = this.modelInterface.getClassPool();
		final CtClass modelImpl = createFromTemplate(classPool);

		modelImpl.addInterface( this.modelInterface );
		
		appendGettersAndSetters(modelImpl);

		try {
			return modelImpl.toClass();
		} catch (final CannotCompileException e) {
			throw new EntityInterfaceIsBrokenException();
		}
	}

	private void appendGettersAndSetters(final CtClass modelImpl)
			throws EntityInterfaceIsBrokenException {
		for (final EntityFieldGenerator fieldGen : this.fields) {
			appendField(modelImpl, fieldGen);
			appendGetter(modelImpl, fieldGen);
			appendSetter(modelImpl, fieldGen);
		}
	}

	private void appendSetter(final CtClass modelImpl, final EntityFieldGenerator fieldGen) throws EntityInterfaceIsBrokenException {
		try {
			final String methodCode = fieldGen.createSetter();
			final CtMethod setter = CtMethod.make(methodCode, modelImpl);
			
			modelImpl.addMethod( setter );
		} catch (final CannotCompileException e) {
			throw new EntityInterfaceIsBrokenException();
		}
	}

	private void appendGetter(final CtClass modelImpl,
			final EntityFieldGenerator fieldGen) throws EntityInterfaceIsBrokenException {
		try {
			final String methodCode = fieldGen.createGetter();
			final CtMethod getter = CtMethod.make(methodCode, modelImpl);
			
			modelImpl.addMethod( getter );
		} catch (final CannotCompileException e) {
			e.printStackTrace();
			throw new EntityInterfaceIsBrokenException();
		}
	}

	private void appendField(final CtClass modelImpl,
			final EntityFieldGenerator fieldGen) throws EntityInterfaceIsBrokenException {
		final String fieldStr = fieldGen.createField();
		try {
			final CtField field = CtField.make(fieldStr, modelImpl);
			modelImpl.addField(field);
		} catch (final CannotCompileException e) {
			throw new EntityInterfaceIsBrokenException();
		}
	}

	private CtClass createFromTemplate(final ClassPool classPool)
			throws EntityInterfaceIsBrokenException {
		try {
			final String tmpName = DefaultEntity.class.getName();
			final CtClass classTmp = classPool.get(tmpName);

			final String modelItfImplName = calcClassName();
			classTmp.setName(modelItfImplName);

			return classTmp;
		} catch (final NotFoundException e) {
			throw new EntityInterfaceIsBrokenException();
		}
	}

	private String calcClassName() {
		final String itfName = this.modelInterface.getName();

		return itfName + "Impl";
	}

}
